﻿//*****************************************************************************
//
//  Copyright 2008 IT Cloud Company
//
//  This software is available under the Microsoft Public License.  Please
//  read the license agreement at http://opensource.org/licenses/ms-pl.html
//  before using the software.
//
//  Authors:
//    Don Song (don@itcloud.com)
//
//*****************************************************************************

using System;
using System.Diagnostics;
using System.Windows.Controls;
using System.Windows.Input;
using System.Windows.Media.Animation;
using System.Windows;
using System.Windows.Media;

namespace HeadLinx.AD.Explorer
{
    public class PanZoomViewer : ContentControl
    {
        public double DefaultZoomFactor { get; set; }
        private FrameworkElement source;
        private Point ScreenStartPoint = new Point(0, 0);
        private TranslateTransform translateTransform;
        private ScaleTransform zoomTransform;
        private TransformGroup transformGroup;
        private Point startOffset;

        public PanZoomViewer()
        {
            this.DefaultZoomFactor = 1.25;
            this.ClipToBounds = true;
        }

        public override void OnApplyTemplate()
        {
            base.OnApplyTemplate();
            Setup(this);
        }

        void Setup(FrameworkElement control)
        {
            this.source = VisualTreeHelper.GetChild(this, 0) as FrameworkElement;

            this.translateTransform = new TranslateTransform();
            this.zoomTransform = new ScaleTransform();
            this.transformGroup = new TransformGroup();
            this.transformGroup.Children.Add(this.zoomTransform);
            this.transformGroup.Children.Add(this.translateTransform);
            this.source.RenderTransform = this.transformGroup;
            this.Focusable = true;
            this.MouseMove += new MouseEventHandler(control_MouseMove);
            this.MouseDown += new MouseButtonEventHandler(source_MouseDown);
            this.MouseUp += new MouseButtonEventHandler(source_MouseUp);
            this.MouseWheel += new MouseWheelEventHandler(source_MouseWheel);
        }

        public void ZoomIn()
        {
            double zoomFactor = this.DefaultZoomFactor;
            var physicalPoint = new Point(this.ActualWidth / 2, this.ActualHeight / 2);
            DoZoom(zoomFactor, this.transformGroup.Inverse.Transform(physicalPoint), physicalPoint);
        }

        public void ZoomOut()
        {
            double zoomFactor = 1.0 / this.DefaultZoomFactor;
            var physicalPoint = new Point(this.ActualWidth / 2, this.ActualHeight / 2);
            DoZoom(zoomFactor, this.transformGroup.Inverse.Transform(physicalPoint), physicalPoint);
        }

        public void PanLeft(double distance)
        {
            this.translateTransform.X += (distance * this.zoomTransform.ScaleX);
        }

        public void PanRight(double distance)
        {
            this.translateTransform.X -= (distance * this.zoomTransform.ScaleX);
        }

        public void PanUp(double distance)
        {
            this.translateTransform.Y += (distance * this.zoomTransform.ScaleY);
        }

        public void PanDown(double distance)
        {
            this.translateTransform.Y -= (distance * this.zoomTransform.ScaleY);
        }

        void source_MouseWheel(object sender, MouseWheelEventArgs e)
        {
            // zoom into the content.  Calculate the zoom factor based on the direction of the mouse wheel.

            double zoomFactor = this.DefaultZoomFactor;

            if (e.Delta <= 0)
            {
                zoomFactor = 1.0 / this.DefaultZoomFactor;
            }

            // DoZoom requires both the logical and physical location of the mouse pointer
            var physicalPoint = e.GetPosition(this);
            DoZoom(zoomFactor, this.transformGroup.Inverse.Transform(physicalPoint), physicalPoint);
        }

        void source_MouseUp(object sender, MouseButtonEventArgs e)
        {
            if (this.IsMouseCaptured)
            {
                // we're done.  reset the cursor and release the mouse pointer
                this.Cursor = Cursors.Arrow;
                this.ReleaseMouseCapture();
            }
        }

        void source_MouseDown(object sender, MouseButtonEventArgs e)
        {
            if (e.ChangedButton == MouseButton.Left)
            {
                // Save starting point, used later when determining how much to scroll.
                this.ScreenStartPoint = e.GetPosition(this);
                this.startOffset = new Point(this.translateTransform.X, this.translateTransform.Y);
                this.CaptureMouse();
                this.Cursor = Cursors.ScrollAll;
            }
        }

        void control_MouseMove(object sender, MouseEventArgs e)
        {
            if (this.IsMouseCaptured)
            {
                var physicalPoint = e.GetPosition(this);
                double newX = physicalPoint.X - this.ScreenStartPoint.X + this.startOffset.X;
                double newY = physicalPoint.Y - this.ScreenStartPoint.Y + this.startOffset.Y;
                SetTranslateTransform(newX, newY);
            }
        }

        private void SetTranslateTransform(double x, double y)
        {
            this.translateTransform.X = x;
            this.translateTransform.Y = y;
        }

        private void SetScaleTransform(double scaleFactor)
        {
            this.zoomTransform.ScaleX = scaleFactor;
            this.zoomTransform.ScaleY = scaleFactor;
        }

        public void DoZoom(double deltaZoom, Point mousePosition, Point physicalPosition)
        {
            double currentZoom = this.zoomTransform.ScaleX;
            currentZoom *= deltaZoom;

            double newX = -1 * (mousePosition.X * currentZoom - physicalPosition.X);
            double newY = -1 * (mousePosition.Y * currentZoom - physicalPosition.Y);
            SetTranslateTransform(newX, newY);
            SetScaleTransform(currentZoom);
        }

        /// <summary>Reset to default zoom level and centered content.</summary>
        public void Reset()
        {
            FrameworkElement c = this.Content as FrameworkElement;

            double scale = 1.0;
            double translateX = 0;
            double translateY = 0;

            if (c != null)
            {
                //
                //  Compute the scale and transform offsets needed to
                //  center and fit the content in the current viewport.
                //

                // Compute scale (zoom level).
                double scaleX = this.ActualWidth / c.ActualWidth;
                double scaleY = this.ActualHeight / c.ActualHeight;
                scale = Math.Min(scaleX, scaleY);

                if (scale < 1.0)
                {
                    // Need to shrink the content.

                    // Compute transform offsets (position).
                    double scaledWidthDelta = this.ActualWidth - (c.ActualWidth * scale);
                    double scaledHeightDelta = this.ActualHeight - (c.ActualHeight * scale);

                    if (scaledWidthDelta > scaledHeightDelta)
                    {
                        translateX = scaledWidthDelta / 2.0;
                    }
                    else
                    {
                        translateY = scaledHeightDelta / 2.0;
                    }
                }
                else
                {
                    // Content already fits in the view so don't scale it,
                    // just center it.
                    scale = 1.0;
                    translateX = 0;
                    translateY = 0;
                }
            }

            SetTranslateTransform(translateX, translateY);
            SetScaleTransform(scale);
        }
    }
}
